/*
 * @(#)Shell.java	1.0 Apr 18, 2008
 *
 *	The MIT License
 *
 *	Copyright (c) 2008 Malachi de AElfweald <malachid@gmail.com>
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */
package org.eoti.io.console.shell;

import org.eoti.io.args4j.Args4jRegistry;
import org.eoti.io.console.Terminal;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.ExampleMode;

import java.util.*;
import java.util.concurrent.*;
import java.io.IOException;

public class Shell extends Terminal
{
	static{Args4jRegistry.install();}

	// receives whatever non-option arguments are passed
	@Argument
	protected List<String> arguments = new ArrayList<String>();

	protected static final String UNKNOWN_FMT = "UNKNOWN COMMAND: '%s'";
	protected String name = "Shell";
	protected ConcurrentHashMap<String,ShellExtension> extensions;
	protected boolean done = false;
	protected boolean loggedIn = false;

	public Shell(String ... args)
			throws CmdLineException, IOException
	{
		super();
		CmdLineParser cmdLineParser = new CmdLineParser(this);
		try{
			cmdLineParser.parseArgument(args);
			parseCommandLine();
		} catch(CmdLineException e){
			System.err.format("Error: %s\n", e.getMessage());
			System.err.format("USAGE: java %s [options...] arguments...\n", getClass().getName());
			cmdLineParser.printUsage(System.err);
			System.err.println();

			System.err.format(
				"EXAMPLE: java %s %s\n",
				getClass().getName(),
				cmdLineParser.printExample(ExampleMode.ALL)
			);

			System.exit(1);
		}

		resetEnvironment();
	}

	protected void parseCommandLine() throws CmdLineException{}

	/**
	 * Load the default extensions
	 */
	protected void loadExtensions()
	{
		loadExtensions(ShellExtension.class);
	}

	/**
	 * Load a specific subset of extensions
	 * @param cls type of extensions to load
	 */
	protected void loadExtensions(Class<ShellExtension> cls)
	{
		extensions = new ConcurrentHashMap<String,ShellExtension>();

		ServiceLoader<ShellExtension> loader = ServiceLoader.load(cls);
		for(ShellExtension ext : loader)
			register(ext);
	}

	public void register(ShellExtension extension)
	{
		extension.setShell(this);
		extensions.put(extension.getName(), extension);
	}

	public Map<String,ShellExtension> getExtensions(){return extensions;}

	public void quit()
	{
		done = true;
	}

	protected String getLoginPrompt(){return "login: ";}
	protected String getPasswordPrompt(){return "password: ";}

	/**
	 * @return message to display on valid authorization
	 * @throws ShellException  on failed authorization
	 */
	protected String authorize(String username, char[] password)
			throws ShellException
	{
		return "Login Successful";
	}

	public void logOut(){loggedIn = false;}

	protected void authorize()
			throws ShellException
	{
		if(loggedIn) return;

		String login = console.readLine(getLoginPrompt());
		char[] pwd = console.readPassword(getPasswordPrompt());
		console.format("%s\n", authorize(login, pwd));
		loggedIn = true;
	}

	public void startShell()
	{
		if(extensions == null)
			loadExtensions();

		while(!done)
		{
			try{
				authorize();
				List<String> args = promptArgs();
				if(!args.isEmpty()) execute(args);
			}catch(ShellException se){
				error(se, se.shouldShowStackTrace());
			}catch(Exception e){
				error(e);
			}
		}
	}

	public ShellExtension getExtension(String name)
	{
		return extensions.get(name);
	}

	protected void execute(List<String> args)
			throws ShellException
	{
		String cmd = args.remove(0);
		if(cmd.trim().length() == 0) return;

		ShellExtension ext = getExtension(cmd);
		if(ext == null)
			throw new ShellException(this, null, String.format(UNKNOWN_FMT, cmd), false);

		ext.execute(args);
	}

	protected void setName(String name){this.name = name;}
	public String getName(){return name;}
}
